﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Dqaqs.Implemenation.Basic
{
	internal class DqQuerySqlProvider : IDqQuerySqlProvider
	{
		public string GetSqlQuery(string tableName, IDqQuery query)
		{
			var rv = new StringBuilder();

			rv.Append("SELECT ");

			var firstInLoop = true;
			foreach (var dqProjection in query.Projections)
			{
				if (!firstInLoop)
					rv.Append(", ");
				rv.Append(SelectColumn(dqProjection));
				firstInLoop = false;
			}

			rv.AppendFormat(" FROM [{0}] ", tableName);

			if (query.Selections.Any())
			{
				rv.Append(" WHERE ");

				firstInLoop = true;
				foreach (var selection in query.Selections)
				{
					if (!firstInLoop)
						rv.AppendFormat(" {0} ", GetConditionOperatorString(selection.Item1));
					rv.AppendFormat(" {0} ", GetConditionString(selection.Item2));
					firstInLoop = false;
				}
			}

			return rv.ToString();
		}

		private static string GetConditionString(SelectionCondition exp)
		{
			if (exp.Expression is BinaryExpression)
				return GetBinaryExpressionString((BinaryExpression)exp.Expression);
			throw new NotSupportedException(String.Format("Expression {0} is not supported.", exp));
		}

		private static string GetBinaryExpressionString(BinaryExpression exp)
		{
			var op = default(string);
			switch (exp.NodeType)
			{
				case ExpressionType.Equal :
					op = "=";
					break;
				case ExpressionType.GreaterThan :
					op = ">";
					break;
				case ExpressionType.GreaterThanOrEqual :
					op = ">=";
					break;
				case ExpressionType.LessThan :
					op = "<";
					break;
				case ExpressionType.LessThanOrEqual :
					op = " <=";
					break;
				case ExpressionType.NotEqual :
					op = "<>";
					break;
				default:
					throw new NotSupportedException(String.Format("Expression of type {0} is not supported.", exp.NodeType));
			}

			var left = GetConstantOrParameterString(exp.Left);
			var right = GetConstantOrParameterString(exp.Right);

			return String.Format("{0} {1} {2}", left, op, right);
		}

		private static string GetConstantOrParameterString(Expression exp)
		{
			if (exp is ConstantExpression)
			{
				var cexp = (ConstantExpression) exp;
				var delim = (cexp.Value is string || cexp.Value is DateTime) ? "'" : "";
				return String.Format("{0}{1}{0}", delim, cexp.Value);
			}
			if (exp is ParameterExpression)
			{
				var pexp = (ParameterExpression) exp;
				return String.Format("[{0}]", pexp.Name);
			}
			throw new NotSupportedException(
				String.Format("Expression of type {0} is not supported as part of a binary expression", exp.NodeType));
		}

		private static string GetConditionOperatorString(SelectionOperand op)
		{
			return op.ToString();
		}

		private static string SelectColumn(DqProjection dqProjection)
		{
			return String.Format("[{0}]", dqProjection.Column.ColumnName);
		}
	}
}